/*
 * FreeRTOS V202212.00
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

/*
 * This demo application creates six co-routines and two tasks (three including
 * the idle task).  The co-routines execute as part of the idle task hook.
 *
 * Five of the created co-routines are the standard 'co-routine flash'
 * co-routines contained within the Demo/Common/Minimal/crflash.c file and
 * documented on the FreeRTOS.org WEB site.
 *
 * The 'LCD Task' rotates a string on the LCD, delaying between each character
 * as necessitated by the slow interface, and delaying between each string just
 * long enough to enable the text to be read.
 *
 * The sixth co-routine and final task control the transmission and reception
 * of a string to UART 0.  The co-routine periodically sends the first
 * character of the string to the UART, with the UART's TxEnd interrupt being
 * used to transmit the remaining characters.  The UART's RxEnd interrupt
 * receives the characters and places them on a queue to be processed by the
 * 'COMs Rx' task.  An error is latched should an unexpected character be
 * received, or any character be received out of sequence.
 *
 * A loopback connector is required to ensure that each character transmitted
 * on the UART is also received on the same UART.  For test purposes the UART
 * FIFO's are not utalised in order to maximise the interrupt overhead.  Also
 * a pseudo random interval is used between the start of each transmission in
 * order that the resultant interrupts are more randomly distributed and
 * therefore more likely to highlight any problems.
 *
 * The flash co-routines control LED's zero to four.  LED five is toggled each
 * time the string is transmitted on the UART.  LED six is toggled each time
 * the string is CORRECTLY received on the UART.  LED seven is latched on should
 * an error be detected in any task or co-routine.
 *
 * In addition the idle task makes repetitive calls to
 * prvSetAndCheckRegisters().  This simply loads the general purpose registers
 * with a known value, then checks each register to ensure the held value is
 * still correct.  As a low priority task this checking routine is likely to
 * get repeatedly swapped in and out.  A register being found to contain an
 * incorrect value is therefore indicative of an error in the task switching
 * mechansim.
 *
 */

/* Scheduler include files. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "croutine.h"

/* Demo application include files. */
#include "partest.h"
#include "crflash.h"

/* Library include files. */
#include "DriverLib.h"

/* The time to delay between writing each character to the LCD. */
#define mainCHAR_WRITE_DELAY          ( 2 / portTICK_PERIOD_MS )

/* The time to delay between writing each string to the LCD. */
#define mainSTRING_WRITE_DELAY        ( 400 / portTICK_PERIOD_MS )

/* The number of flash co-routines to create. */
#define mainNUM_FLASH_CO_ROUTINES     ( 5 )

/* The length of the queue used to pass received characters to the Comms Rx
 * task. */
#define mainRX_QUEUE_LEN              ( 5 )

/* The priority of the co-routine used to initiate the transmission of the
 * string on UART 0. */
#define mainTX_CO_ROUTINE_PRIORITY    ( 1 )

/* Only one co-routine is created so its index is not important. */
#define mainTX_CO_ROUTINE_INDEX       ( 0 )

/* The time between transmissions of the string on UART 0.   This is pseudo
 * random in order to generate a bit or randomness to when the interrupts occur.*/
#define mainMIN_TX_DELAY              ( 40 / portTICK_PERIOD_MS )
#define mainMAX_TX_DELAY              ( ( TickType_t ) 0x7f )
#define mainOFFSET_TIME               ( ( TickType_t ) 3 )

/* The time the Comms Rx task should wait to receive a character.  This should
 * be slightly longer than the time between transmissions.  If we do not receive
 * a character after this time then there must be an error in the transmission or
 * the timing of the transmission. */
#define mainCOMMS_RX_DELAY            ( mainMAX_TX_DELAY + 20 )

/* The task priorities. */
#define mainLCD_TASK_PRIORITY         ( tskIDLE_PRIORITY )
#define mainCOMMS_RX_TASK_PRIORITY    ( tskIDLE_PRIORITY + 1 )

/* The LED's toggled by the various tasks. */
#define mainCOMMS_FAIL_LED            ( 7 )
#define mainCOMMS_RX_LED              ( 6 )
#define mainCOMMS_TX_LED              ( 5 )

/* The baud rate used by the UART comms tasks/co-routine. */
#define mainBAUD_RATE                 ( 57600 )

/* FIFO setting for the UART.  The FIFO is not used to create a better test. */
#define mainFIFO_SET                  ( 0x10 )

/* The string that is transmitted on the UART contains sequentially the
 * characters from mainFIRST_TX_CHAR to mainLAST_TX_CHAR. */
#define mainFIRST_TX_CHAR             '0'
#define mainLAST_TX_CHAR              'z'

/* Just used to walk through the program memory in order that some random data
 * can be generated. */
#define mainTOTAL_PROGRAM_MEMORY      ( ( unsigned long * ) ( 8 * 1024 ) )
#define mainFIRST_PROGRAM_BYTES       ( ( unsigned long * ) 4 )

/* The error routine that is called if the driver library encounters an error. */
#ifdef DEBUG
    void __error__( char * pcFilename,
                    unsigned long ulLine )
    {
    }
#endif

/*-----------------------------------------------------------*/

/*
 * The task that rotates text on the LCD.
 */
static void vLCDTask( void * pvParameters );

/*
 * The task that receives the characters from UART 0.
 */
static void vCommsRxTask( void * pvParameters );

/*
 * The co-routine that periodically initiates the transmission of the string on
 * the UART.
 */
static void vSerialTxCoRoutine( CoRoutineHandle_t xHandle,
                                unsigned portBASE_TYPE uxIndex );

/*
 * Writes a string the the LCD.
 */
static void prvWriteString( const char * pcString );

/*
 * Initialisation routine for the UART.
 */
static void vSerialInit( void );

/*
 * Thread safe write to the PDC.
 */
static void prvPDCWrite( char cAddress,
                         char cData );

/*
 * Function to simply set a known value into the general purpose registers
 * then read them back to ensure they remain set correctly.  An incorrect value
 * being indicative of an error in the task switching mechanism.
 */
void prvSetAndCheckRegisters( void );

/*
 * Latch the LED that indicates that an error has occurred.
 */
void vSetErrorLED( void );

/*
 * Sets up the PLL and ports used by the demo.
 */
static void prvSetupHardware( void );

/*-----------------------------------------------------------*/

/* Error flag set to pdFAIL if an error is encountered in the tasks/co-routines
 * defined within this file. */
unsigned portBASE_TYPE uxErrorStatus = pdPASS;

/* The next character to transmit. */
static char cNextChar;

/* The queue used to transmit characters from the interrupt to the Comms Rx
 * task. */
static QueueHandle_t xCommsQueue;

/*-----------------------------------------------------------*/

void Main( void )
{
    /* Create the queue used to communicate between the UART ISR and the Comms
     * Rx task. */
    xCommsQueue = xQueueCreate( mainRX_QUEUE_LEN, sizeof( char ) );

    /* Setup the ports used by the demo and the clock. */
    prvSetupHardware();

    /* Create the co-routines that flash the LED's. */
    vStartFlashCoRoutines( mainNUM_FLASH_CO_ROUTINES );

    /* Create the co-routine that initiates the transmission of characters
     * on the UART. */
    xCoRoutineCreate( vSerialTxCoRoutine, mainTX_CO_ROUTINE_PRIORITY, mainTX_CO_ROUTINE_INDEX );

    /* Create the LCD and Comms Rx tasks. */
    xTaskCreate( vLCDTask, "LCD", configMINIMAL_STACK_SIZE, NULL, mainLCD_TASK_PRIORITY, NULL );
    xTaskCreate( vCommsRxTask, "CMS", configMINIMAL_STACK_SIZE, NULL, mainCOMMS_RX_TASK_PRIORITY, NULL );

    /* Start the scheduler running the tasks and co-routines just created. */
    vTaskStartScheduler();

    /* Should not get here unless we did not have enough memory to start the
     * scheduler. */
    for( ; ; )
    {
    }
}
/*-----------------------------------------------------------*/

static void prvSetupHardware( void )
{
    /* Setup the PLL. */
    SysCtlClockSet( SYSCTL_SYSDIV_10 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_6MHZ );

    /* Initialise the hardware used to talk to the LCD, LED's and UART. */
    PDCInit();
    vParTestInitialise();
    vSerialInit();
}
/*-----------------------------------------------------------*/

void vApplicationIdleHook( void )
{
    /* The co-routines are executed in the idle task using the idle task
     * hook. */
    for( ; ; )
    {
        /* Schedule the co-routines. */
        vCoRoutineSchedule();

        /* Run the register check function between each co-routine. */
        prvSetAndCheckRegisters();
    }
}
/*-----------------------------------------------------------*/

static void prvWriteString( const char * pcString )
{
    /* Write pcString to the LED, pausing between each character. */
    prvPDCWrite( PDC_LCD_CSR, LCD_CLEAR );

    while( *pcString )
    {
        vTaskDelay( mainCHAR_WRITE_DELAY );
        prvPDCWrite( PDC_LCD_RAM, *pcString );
        pcString++;
    }
}
/*-----------------------------------------------------------*/

void vLCDTask( void * pvParameters )
{
    unsigned portBASE_TYPE uxIndex;
    const unsigned char ucCFGData[] =
    {
        0x30, /* Set data bus to 8-bits. */
        0x30,
        0x30,
        0x3C, /* Number of lines/font. */
        0x08, /* Display off. */
        0x01, /* Display clear. */
        0x06, /* Entry mode [cursor dir][shift]. */
        0x0C  /* Display on [display on][curson on][blinking on]. */
    };

/* The strings that are written to the LCD. */
    const char * pcStringsToDisplay[] =
    {
        "Stellaris",
        "Demo",
        "One",
        "www.FreeRTOS.org",
        ""
    };

    /* Configure the LCD. */
    uxIndex = 0;

    while( uxIndex < sizeof( ucCFGData ) )
    {
        prvPDCWrite( PDC_LCD_CSR, ucCFGData[ uxIndex ] );
        uxIndex++;
        vTaskDelay( mainCHAR_WRITE_DELAY );
    }

    /* Turn the LCD Backlight on. */
    prvPDCWrite( PDC_CSR, 0x01 );

    /* Clear display. */
    vTaskDelay( mainCHAR_WRITE_DELAY );
    prvPDCWrite( PDC_LCD_CSR, LCD_CLEAR );

    uxIndex = 0;

    for( ; ; )
    {
        /* Display the string on the LCD. */
        prvWriteString( pcStringsToDisplay[ uxIndex ] );

        /* Move on to the next string - wrapping if necessary. */
        uxIndex++;

        if( *( pcStringsToDisplay[ uxIndex ] ) == 0x00 )
        {
            uxIndex = 0;
            /* Longer pause on the last string to be sent. */
            vTaskDelay( mainSTRING_WRITE_DELAY * 2 );
        }

        /* Wait until it is time to move onto the next string. */
        vTaskDelay( mainSTRING_WRITE_DELAY );
    }
}
/*-----------------------------------------------------------*/

static void vCommsRxTask( void * pvParameters )
{
    static char cRxedChar, cExpectedChar;

    /* Set the char we expect to receive to the start of the string. */
    cExpectedChar = mainFIRST_TX_CHAR;

    for( ; ; )
    {
        /* Wait for a character to be received. */
        xQueueReceive( xCommsQueue, ( void * ) &cRxedChar, mainCOMMS_RX_DELAY );

        /* Was the character received (if any) the expected character. */
        if( cRxedChar != cExpectedChar )
        {
            /* Got an unexpected character.  This can sometimes occur when
             * reseting the system using the debugger leaving characters already
             * in the UART registers. */
            uxErrorStatus = pdFAIL;

            /* Resync by waiting for the end of the current string. */
            while( cRxedChar != mainLAST_TX_CHAR )
            {
                while( !xQueueReceive( xCommsQueue, ( void * ) &cRxedChar, portMAX_DELAY ) )
                {
                }
            }

            /* The next expected character is the start of the string again. */
            cExpectedChar = mainFIRST_TX_CHAR;
        }
        else
        {
            if( cExpectedChar == mainLAST_TX_CHAR )
            {
                /* We have reached the end of the string - we now expect to
                 * receive the first character in the string again.   The LED is
                 * toggled to indicate that the entire string was received without
                 * error. */
                vParTestToggleLED( mainCOMMS_RX_LED );
                cExpectedChar = mainFIRST_TX_CHAR;
            }
            else
            {
                /* We got the expected character, we now expect to receive the
                 * next character in the string. */
                cExpectedChar++;
            }
        }
    }
}
/*-----------------------------------------------------------*/

static void vSerialTxCoRoutine( CoRoutineHandle_t xHandle,
                                unsigned portBASE_TYPE uxIndex )
{
    TickType_t xDelayPeriod;
    static unsigned long * pulRandomBytes = mainFIRST_PROGRAM_BYTES;

    /* Co-routine MUST start with a call to crSTART. */
    crSTART( xHandle );

    for( ; ; )
    {
        /* Was the previously transmitted string received correctly? */
        if( uxErrorStatus != pdPASS )
        {
            /* An error was encountered so set the error LED. */
            vSetErrorLED();
        }

        /* The next character to Tx is the first in the string. */
        cNextChar = mainFIRST_TX_CHAR;

        UARTIntDisable( UART0_BASE, UART_INT_TX );
        {
            /* Send the first character. */
            if( !( HWREG( UART0_BASE + UART_O_FR ) & UART_FR_TXFF ) )
            {
                HWREG( UART0_BASE + UART_O_DR ) = cNextChar;
            }

            /* Move the variable to the char to Tx on so the ISR transmits
             * the next character in the string once this one has completed. */
            cNextChar++;
        }
        UARTIntEnable( UART0_BASE, UART_INT_TX );

        /* Toggle the LED to show a new string is being transmitted. */
        vParTestToggleLED( mainCOMMS_TX_LED );

        /* Delay before we start the string off again.  A pseudo-random delay
         * is used as this will provide a better test. */
        xDelayPeriod = xTaskGetTickCount() + ( *pulRandomBytes );

        pulRandomBytes++;

        if( pulRandomBytes > mainTOTAL_PROGRAM_MEMORY )
        {
            pulRandomBytes = mainFIRST_PROGRAM_BYTES;
        }

        /* Make sure we don't wait too long... */
        xDelayPeriod &= mainMAX_TX_DELAY;

        /* ...but we do want to wait. */
        if( xDelayPeriod < mainMIN_TX_DELAY )
        {
            xDelayPeriod = mainMIN_TX_DELAY;
        }

        /* Block for the random(ish) time. */
        crDELAY( xHandle, xDelayPeriod );
    }

    /* Co-routine MUST end with a call to crEND. */
    crEND();
}
/*-----------------------------------------------------------*/

static void vSerialInit( void )
{
    /* Enable the UART.  GPIOA has already been initialised. */
    SysCtlPeripheralEnable( SYSCTL_PERIPH_UART0 );

    /* Set GPIO A0 and A1 as peripheral function.  They are used to output the
     * UART signals. */
    GPIODirModeSet( GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1, GPIO_DIR_MODE_HW );

    /* Configure the UART for 8-N-1 operation. */
    UARTConfigSet( UART0_BASE, mainBAUD_RATE, UART_CONFIG_WLEN_8 | UART_CONFIG_PAR_NONE | UART_CONFIG_STOP_ONE );

    /* We dont want to use the fifo.  This is for test purposes to generate
     * as many interrupts as possible. */
    HWREG( UART0_BASE + UART_O_LCR_H ) &= ~mainFIFO_SET;

    /* Enable both Rx and Tx interrupts. */
    HWREG( UART0_BASE + UART_O_IM ) |= ( UART_INT_TX | UART_INT_RX );
    IntEnable( INT_UART0 );
}
/*-----------------------------------------------------------*/

void vUART_ISR( void )
{
    unsigned long ulStatus;
    char cRxedChar;
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;

    /* What caused the interrupt. */
    ulStatus = UARTIntStatus( UART0_BASE, pdTRUE );

    /* Clear the interrupt. */
    UARTIntClear( UART0_BASE, ulStatus );

    /* Was an Rx interrupt pending? */
    if( ulStatus & UART_INT_RX )
    {
        if( ( HWREG( UART0_BASE + UART_O_FR ) & UART_FR_RXFF ) )
        {
            /* Get the char from the buffer and post it onto the queue of
             * Rxed chars.  Posting the character should wake the task that is
             * blocked on the queue waiting for characters. */
            cRxedChar = ( char ) HWREG( UART0_BASE + UART_O_DR );
            xQueueSendFromISR( xCommsQueue, &cRxedChar, &xHigherPriorityTaskWoken );
        }
    }

    /* Was a Tx interrupt pending? */
    if( ulStatus & UART_INT_TX )
    {
        /* Send the next character in the string.  We are not using the FIFO. */
        if( cNextChar <= mainLAST_TX_CHAR )
        {
            if( !( HWREG( UART0_BASE + UART_O_FR ) & UART_FR_TXFF ) )
            {
                HWREG( UART0_BASE + UART_O_DR ) = cNextChar;
            }

            cNextChar++;
        }
    }

    /* If a task was woken by the character being received then we force
     * a context switch to occur in case the task is of higher priority than
     * the currently executing task (i.e. the task that this interrupt
     * interrupted.) */
    portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

static void prvPDCWrite( char cAddress,
                         char cData )
{
    vTaskSuspendAll();
    {
        PDCWrite( cAddress, cData );
    }
    xTaskResumeAll();
}
/*-----------------------------------------------------------*/

void vSetErrorLED( void )
{
    vParTestSetLED( mainCOMMS_FAIL_LED, pdTRUE );
}
/*-----------------------------------------------------------*/

void prvSetAndCheckRegisters( void )
{
    /* Fill the general purpose registers with known values. */
    __asm volatile ( "    mov r11, #10\n"
                     "    add r0, r11, #1\n"
                     "    add r1, r11, #2\n"
                     "    add r2, r11, #3\n"
                     "    add r3, r11, #4\n"
                     "    add r4, r11, #5\n"
                     "    add r5, r11, #6\n"
                     "    add r6, r11, #7\n"
                     "    add r7, r11, #8\n"
                     "    add r8, r11, #9\n"
                     "    add r9, r11, #10\n"
                     "    add r10, r11, #11\n"
                     "    add r12, r11, #12" );

    /* Check the values are as expected. */
    __asm volatile ( "    cmp r11, #10\n"
                     "    bne set_error_led\n"
                     "    cmp r0, #11\n"
                     "    bne set_error_led\n"
                     "    cmp r1, #12\n"
                     "    bne set_error_led\n"
                     "    cmp r2, #13\n"
                     "    bne set_error_led\n"
                     "    cmp r3, #14\n"
                     "    bne set_error_led\n"
                     "    cmp r4, #15\n"
                     "    bne set_error_led\n"
                     "    cmp r5, #16\n"
                     "    bne set_error_led\n"
                     "    cmp r6, #17\n"
                     "    bne set_error_led\n"
                     "    cmp r7, #18\n"
                     "    bne set_error_led\n"
                     "    cmp r8, #19\n"
                     "    bne set_error_led\n"
                     "    cmp r9, #20\n"
                     "    bne set_error_led\n"
                     "    cmp r10, #21\n"
                     "    bne set_error_led\n"
                     "    cmp r12, #22\n"
                     "    bne set_error_led\n"
                     "    bx lr" );

    __asm volatile ( "set_error_led:\n"
                     "    push {r14}\n"
                     "    ldr r1, =vSetErrorLED\n"
                     "    blx r1\n"
                     "    pop {r14}\n"
                     "    bx lr" );
}
/*-----------------------------------------------------------*/
